package xj.toolkit.unit;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.serializer.SerializerFeature;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.util.ReflectionUtils;

import xj.toolkit.ant.SelectorUtils;
import xj.toolkit.jmx.MBeanRegisterSupport;
import xj.toolkit.util.ExceptionUtils;
import xj.toolkit.util.PackageUtils;
import xj.toolkit.util.Pair;
import xj.toolkit.util.Triple;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class UnitAdmin implements UnitAdminMXBean {

	private final static Comparator<UnitContext> UNITCTX_ASCEND_COMPARATOR = new Comparator<UnitContext>() {
		@Override
		public int compare(final UnitContext o1, final UnitContext o2) {
			return o1.getOrder() - o2.getOrder();
		}
	};

	private static final String _DEFAULT_GROUP = "default";

	private final static String[] _DEFAULT_SOURCE_PATTERNS = new String[] { "**/bizflow/**.xml",
			"xt/template/transport/**.xml", "unitrepo/**/protocol/**.xml" };

	public static interface UnitMXBean {

		public String getUnitId();

		public String getGroup();

		public void setGroup(final String group);

		public String getName();

		public void setName(final String name);

		public String getSource();

		public Map<String, String> getParameters();

		public Map<String, String> getPlaceholders();

		public String getCreateTimestamp();

		public void setOrder(final int order);

		public int getOrder();

		public void close();
	}

	public static interface SourceMXBean {

		public Map<String, String> getPlaceholders();

	}

	private static final class StopInitCtxException extends RuntimeException {

		private static final long serialVersionUID = 1L;
	}

	private static final Logger LOG = LoggerFactory.getLogger(UnitAdmin.class);

	public UnitAdmin(final ApplicationContext root) {
		this._root = root;
		this._sourcePatterns = _DEFAULT_SOURCE_PATTERNS;
		this._sourcesRegister = new MBeanRegisterSupport("xj.toolkit:type=unitSource", null);
		this._unitsRegister = new MBeanRegisterSupport("xj.toolkit:type=units", null);
		// 设置工程全局配置文件
		PropertyPlaceholderConfigurer rootConfigurer = this._root.getBean(PropertyPlaceholderConfigurer.class);
		if (rootConfigurer != null) {
			Field field = ReflectionUtils.findField(rootConfigurer.getClass(), "locations");
			field.setAccessible(true);
			this._rootPropertyFiles = (Resource[]) ReflectionUtils.getField(field, rootConfigurer);
		} else {
			this._rootPropertyFiles = null;
		}
	}

	public void setJsonPath(final String path) {
		this._jsonPath = path;
	}

	public void setRestoreUnitsFromJson(final boolean enabled) {
		this._restoreUnitsFromJson = enabled;
	}

	public boolean getRestoreUnitsFromJson() {
		return this._restoreUnitsFromJson;
	}

	public void init() {
		refreshSources();

		if (this._restoreUnitsFromJson) {
			restoreUnitsFromJson(activedUnitsJsonsDir());
		} else {
			if (LOG.isInfoEnabled()) {
				LOG.info("restore units from json disabled, ignore units restore progress.");
			}
		}
	}

	private void refreshSources() {
		// this._unitsRegister.destroy();
		final Map<String, Map<String, String>> infos = getSourceInfo(this._sourcePatterns);
		for (Map.Entry<String, Map<String, String>> entry : infos.entrySet()) {
			final Map<String, String> placeholders = entry.getValue();
			final String suffix = "name=" + entry.getKey();
			if (!this._sourcesRegister.isRegistered(suffix)) {
				this._sourcesRegister.registerMBean(suffix, new SourceMXBean() {

					@Override
					public Map<String, String> getPlaceholders() {
						return placeholders;
					}
				});
			}
		}
	}

	public String[] getSourcePatterns() {
		return _sourcePatterns;
	}

	public void setSourcePatterns(final String[] sourcePatterns) {
		this._sourcePatterns = sourcePatterns;
	}

	public String getVersion() {
		return "0.0.1";
	}

	@Override
	public void newUnit(final String name, final String pattern, final String[] params) throws Exception {
		newUnit(name, pattern, new HashMap<String, String>() {
			private static final long serialVersionUID = 1L;

			{
				// 确保偶数
				for (int idx = 0; idx < (params.length / 2) * 2; idx += 2) {
					this.put(params[idx], params[idx + 1]);
				}
			}
		});
	}

	@Override
	public void newUnit(final String name, final String pattern, final Map<String, String> params) throws Exception {
		newUnit(_DEFAULT_GROUP, name, pattern, params);
	}

	@Override
	public void newUnit(final String group, final String name, final String pattern, final Map<String, String> params)
			throws Exception {
		createUnit(group, name, pattern, params);
	}

	public UnitMXBean createUnit(final String group, final String name, final String springXml,
			final Map<String, String> params) throws Exception {

		final int index = this._logidx.incrementAndGet();
		final String now = new Date().toString();

		// final String[] sources = searchUnitSourceOf(new String[] { springXml
		// });
		//
		// if (null == sources) {
		// LOG.warn("can't found unit source matched {}, newUnit {}/{} failed",
		// springXml, group, name);
		// addLog(Integer.toString(index), now + ": newUnit(" + group + "/" +
		// name
		// + ") failed for can't found source matched (" + springXml + ")");
		// return null;
		// } else if (sources.length > 1) {
		// if (usingFirstWhenMatchedMultiSource) {
		// LOG.warn("found unit source more than one matched {}, using first one
		// to create unit {}/{}", springXml,
		// group, name);
		// } else {
		// LOG.warn("found unit source more than one matched {}, failed to
		// create unit {}/{}", springXml, group,
		// name);
		// addLog(Integer.toString(index),
		// now + ": newUnit(" + group + "/" + name + ") failed for found unit
		// source > 1 " + springXml);
		// return null;
		// }
		// }

		final String objectNameSuffix = genUnitSuffix(group, name);
		final Object mock = newMockUnitMXBean(group, name);
		if (!reserveRegistration(objectNameSuffix, mock)) {
			addLog(Integer.toString(index),
					now + ": newUnit(" + group + "/" + name + ") failed for can't reserve " + objectNameSuffix);
			throw new RuntimeException("can't reserve " + objectNameSuffix + ", may be already registered");
		}

		try {
			final ValueAwarePlaceholderConfigurer configurer = new ValueAwarePlaceholderConfigurer() {
				{
					this.setProperties(new Properties() {
						private static final long serialVersionUID = 1L;

						{
							this.putAll(params);
						}
					});

				}
			};

			if (this._rootPropertyFiles != null) {
				configurer.setLocations(this._rootPropertyFiles);
				configurer.setLocalOverride(true);// params(功能单元中的配置)覆盖全局配置
			}

			// final AbstractApplicationContext ctx =
			// createConfigurableApplicationContext(sources[0], configurer);
			final AbstractApplicationContext ctx = createConfigurableApplicationContext(springXml, configurer);

			final UUID uid = UUID.randomUUID();
			// final UnitMXBean unit = newUnitMXBean(uid,
			// this._order.getAndAdd(10), group, name, sources[0], now, params,
			// configurer.getTextedResolvedPlaceholders());
			final UnitMXBean unit = newUnitMXBean(uid, this._order.getAndAdd(10), group, name, springXml, now, params,
					configurer.getTextedResolvedPlaceholders());

			this._units.put(uid, Triple.of(group, name, ctx));

			this._unitsRegister.replaceRegisteredMBean(objectNameSuffix, mock, unit);

			addLog(Integer.toString(index), now + ": newUnit(" + group + "/" + name + ") succeed.");

			return unit;
		} catch (Exception e) {
			this._unitsRegister.unregisterMBean(objectNameSuffix);
			addLog(Integer.toString(index),
					now + ": newUnit(" + group + "/" + name + ") failed for " + ExceptionUtils.exception2detail(e));
			throw e;
		}
	}

	/**
	 * @param uid
	 * @param order
	 * @param group
	 * @param name
	 * @param source
	 * @param now
	 * @param params
	 * @param placeholders
	 * @return
	 */
	private UnitMXBean newUnitMXBean(final UUID uid, final int order, final String group, final String name,
			final String source, final String now, final Map<String, String> params,
			final Map<String, String> placeholders) {
		return new UnitMXBean() {

			@Override
			public String getSource() {
				return source;
			}

			@Override
			public Map<String, String> getParameters() {
				return params;
			}

			@Override
			public Map<String, String> getPlaceholders() {
				return placeholders;
			}

			@Override
			public String getCreateTimestamp() {
				return now;
			}

			@Override
			public void close() {
				deleteUnit(uid);
			}

			@Override
			public int getOrder() {
				return order;
			}

			@Override
			public String getGroup() {
				return group;
			}

			@Override
			public String getName() {
				return name;
			}

			@Override
			public void setGroup(final String _group) {
				if (!_group.equals(group)) {
					if (_unitsRegister.registerMBean(genUnitSuffix(_group, name),
							newUnitMXBean(uid, order, _group, name, source, now, params, placeholders))) {
						_units.put(uid, Triple.of(_group, name, uid2AbstractApplicationContext(uid)));
						_unitsRegister.unregisterMBean(genUnitSuffix(group, name));
					}
				}
			}

			@Override
			public void setName(final String _name) {
				if (!_name.equals(name)) {
					if (_unitsRegister.registerMBean(genUnitSuffix(group, _name),
							newUnitMXBean(uid, order, group, _name, source, now, params, placeholders))) {
						_units.put(uid, Triple.of(group, _name, uid2AbstractApplicationContext(uid)));
						_unitsRegister.unregisterMBean(genUnitSuffix(group, name));
					}
				}
			}

			@Override
			public void setOrder(final int _order) {
				if (_order != order) {
					_unitsRegister.replaceRegisteredMBean(genUnitSuffix(group, name), this,
							newUnitMXBean(uid, _order, group, name, source, now, params, placeholders));
				}
			}

			@Override
			public String getUnitId() {
				return uid.toString();
			}
		};
	}

	private AbstractApplicationContext uid2AbstractApplicationContext(final UUID uid) {
		final Triple<String, String, AbstractApplicationContext> triple = this._units.get(uid);
		return (null != triple ? triple.getThird() : null);
	}

	private Pair<String, String> uid2GroupAndName(final UUID uid) {
		final Triple<String, String, AbstractApplicationContext> triple = this._units.get(uid);
		return (null != triple ? Pair.of(triple.getFirst(), triple.getSecond()) : null);
	}

	private UUID groupAndName2Uid(final String group, final String name) {
		for (Map.Entry<UUID, Triple<String, String, AbstractApplicationContext>> entry : this._units.entrySet()) {
			if (entry.getValue().getFirst().equals(group) && entry.getValue().getSecond().equals(name)) {
				return entry.getKey();
			}
		}
		LOG.warn("can't found unit id matched group={},name={}", group, name);
		return null;
	}

	/**
	 * @param group
	 * @param name
	 * @return
	 */
	private String genUnitSuffix(final String group, final String name) {
		return "group=" + group + ",name=" + name;
	}

	private boolean reserveRegistration(final String objectNameSuffix, final Object mock) {
		return this._unitsRegister.registerMBean(objectNameSuffix, mock);
	}

	private Object newMockUnitMXBean(final String group, final String name) {
		return new UnitMXBean() {

			@Override
			public String getSource() {
				return null;
			}

			@Override
			public Map<String, String> getParameters() {
				return null;
			}

			@Override
			public Map<String, String> getPlaceholders() {
				return null;
			}

			@Override
			public String getCreateTimestamp() {
				return null;
			}

			@Override
			public int getOrder() {
				return 0;
			}

			@Override
			public void close() {
			}

			@Override
			public String getGroup() {
				return group;
			}

			@Override
			public String getName() {
				return name;
			}

			@Override
			public void setGroup(final String group) {
			}

			@Override
			public void setName(final String name) {
			}

			@Override
			public void setOrder(int seq) {
			}

			@Override
			public String getUnitId() {
				return null;
			}
		};
	}

	public void deleteUnit(final UUID uid) {
		final Pair<String, String> pair = uid2GroupAndName(uid);
		final int index = this._logidx.incrementAndGet();
		if (null != pair) {
			this._unitsRegister.unregisterMBean(genUnitSuffix(pair.getFirst(), pair.getSecond()));
			final Triple<String, String, AbstractApplicationContext> triple = this._units.remove(uid);

			if (null != triple) {
				final AbstractApplicationContext ctx = triple.getThird();
				if (null != ctx) {
					ctx.close();
					addLog(Integer.toString(index), new Date().toString() + ": deleteUnit(group=" + pair.getFirst()
							+ ",name=" + pair.getSecond() + ") succeed.)");
				} else {
					LOG.warn("unit id {}'s AbstractApplicationContext is null, unit maybe deleted already", uid);
					addLog(Integer.toString(index), new Date().toString() + ": deleteUnit(group=" + pair.getFirst()
							+ ",name=" + pair.getSecond() + ") failed for AbstractApplicationContext is null.)");
				}
			} else {
				LOG.warn("can't found AbstractApplicationContext matched unit id {}, unit maybe deleted already", uid);
				addLog(Integer.toString(index),
						new Date().toString() + ": deleteUnit(group=" + pair.getFirst() + ",name=" + pair.getSecond()
								+ ") failed for can't found AbstractApplicationContext matched unit id " + uid + ".)");
			}
		} else {
			LOG.warn("can't found group and name matched unit id {}, unit maybe deleted already", uid);
			addLog(Integer.toString(index), new Date().toString() + ": deleteUnit(" + uid
					+ ") failed for can't found group and name matched unit id " + uid + ".)");
		}
	}

	@Override
	public void deleteUnit(final String name) {
		deleteUnit(_DEFAULT_GROUP, name);
	}

	@Override
	public void deleteUnit(final String group, final String name) {
		deleteUnit(groupAndName2Uid(group, name));
	}

	@Override
	public void deleteAllUnit() {
		// remove and close all unit
		while (!this._units.isEmpty()) {
			final UUID uid = this._units.keySet().iterator().next();
			deleteUnit(uid);
		}
	}

	@Override
	public Map<String, Map<String, String>> getSourceInfo(final String sourcePattern) {
		return getSourceInfo(new String[] { sourcePattern });
	}

	public Map<String, Map<String, String>> getSourceInfo(final String[] sourcePatterns) {
		final String[] sources = searchUnitSourceOf(sourcePatterns);

		final Map<String, Map<String, String>> infos = new HashMap<String, Map<String, String>>();

		if (null == sources) {
			LOG.warn("can't found unit source matched {}, getSourcesInfo failed", Arrays.toString(sourcePatterns));
			return infos;
		}

		for (String source : sources) {
			final String unitSource = source;
			final ValueAwarePlaceholderConfigurer configurer = new ValueAwarePlaceholderConfigurer() {
				{
					this.setIgnoreUnresolvablePlaceholders(true);
				}

				@Override
				protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
						throws BeansException {
					super.processProperties(beanFactoryToProcess, props);
					if (LOG.isDebugEnabled()) {
						LOG.debug("after processProperties for source {}", unitSource);
					}
					throw new StopInitCtxException();
				}
			};

			try {
				createConfigurableApplicationContext(source, configurer);
			} catch (StopInitCtxException ignored) {
			}
			infos.put(unitSource, configurer.getTextedResolvedPlaceholders());
		}

		return infos;
	}

	@Override
	public String[] getLogs() {
		return this._logs.toArray(new String[this._logs.size()]);
	}

	@Override
	public void resetLogs() {
		this._logs.clear();
	}

	private void addLog(String id, String msg) {
		this._logs.add(id + ":" + msg + "\r\n");
	}

	@Override
	public String getUnitContextsLocalStorageRootPath() {
		try {
			return new File(this._jsonPath).getCanonicalPath();
		} catch (IOException e) {
			LOG.error("exception when getUnitContextsLocalStorageRootPath, detail: {}",
					ExceptionUtils.exception2detail(e));
			return "<unknown>";
		}
	}

	private List<Object> root2subfilenames(final File root) {
		if (!root.exists()) {
			return null;
		}

		final List<Object> ret = new ArrayList<Object>();

		if (root.isDirectory()) {
			final File[] files = root.listFiles();

			if (null != files) {
				for (File f : files) {
					final List<Object> nodes = root2subfilenames(f);
					if (null != nodes) {
						nodes.add(0, file2name(f));
						ret.add(nodes);
					}
				}
			}
		}

		return ret;
	}

	private String file2name(final File f) {
		return f.isFile() ? f.getName() : "<" + f.getName() + ">";
	}

	@Override
	@SuppressWarnings("unchecked")
	public String getUnitContextsLocalStorageCatalogAsJson() {
		final File localStorageRoot = new File(getUnitContextsLocalStorageRootPath());
		final List<Object> nodes = root2subfilenames(localStorageRoot);
		// sort descending
		Collections.sort(nodes, new Comparator<Object>() {

			@Override
			public int compare(final Object o1, final Object o2) {
				final List<Object> array1 = (List<Object>) o1;
				final List<Object> array2 = (List<Object>) o2;

				if (array1.size() >= 1 && array2.size() >= 1) {
					return compareFilename(array1.get(0).toString(), array2.get(0).toString());
				} else {
					return array1.hashCode() - array2.hashCode();
				}
			}
		});

		try {
			nodes.add(0, "<" + localStorageRoot.getCanonicalPath() + ">");
		} catch (IOException e) {
			LOG.error("exception when localStorageRoot.getCanonicalPath(), detail: {}",
					ExceptionUtils.exception2detail(e));
		}

		return JSON.toJSONString(nodes, SerializerFeature.PrettyFormat);
	}

	/**
	 * @param name1
	 * @param name2
	 * @return
	 */
	private int compareFilename(final String name1, final String name2) {
		if (isDirectory(name1) && !isDirectory(name2)) {
			return -1;
		}
		if (!isDirectory(name1) && isDirectory(name2)) {
			return 1;
		}

		return name2.compareTo(name1);
	}

	private boolean isDirectory(final String name) {
		return name.startsWith("<") && name.endsWith(">");
	}

	@Override
	public void saveUnitContextsToLocalStorage() {
		final String path = genJsonPath();
		try {
			final Map<String, List<UnitContext>> unitCtxs = units2UnitContexts(
					new HashMap<String, List<UnitContext>>());

			for (Map.Entry<String, List<UnitContext>> entry : unitCtxs.entrySet()) {

				Collections.sort(entry.getValue(), UNITCTX_ASCEND_COMPARATOR);

				writeToJson(path + entry.getKey() + ".json", entry.getValue());
			}
		} catch (Exception e) {
			LOG.error("exception when saveUnitContextsToLocalStorage with path {}, detail:{}", path,
					ExceptionUtils.exception2detail(e));
		}
	}

	/**
	 * @param unitCtxs
	 */
	private Map<String, List<UnitContext>> units2UnitContexts(final Map<String, List<UnitContext>> unitCtxs) {
		for (Triple<String, String, AbstractApplicationContext> triple : this._units.values()) {
			final UnitMXBean unit = (UnitMXBean) _unitsRegister
					.getMBean(genUnitSuffix(triple.getFirst(), triple.getSecond()));

			if (null != unit) {
				final List<UnitContext> ctxs = getUnitContexts(unitCtxs, unit.getGroup());

				ctxs.add(new UnitContext()
						// .setGroup(unit.getGroup())
						.setName(unit.getName()).setParams(unit.getParameters()).setPlaceholders(unit.getPlaceholders())
						.setSource(unit.getSource()).setOrder(unit.getOrder()));
			}
		}

		return unitCtxs;
	}

	@Override
	public void restoreUnitsFromLocalStorage(final String path) {
		restoreUnitsFromJson(new File(path));
	}

	private String genJsonPath() {
		final String path = this._jsonPath + "/" + new SimpleDateFormat("yyyyMMddHHmmssSSSS").format(new Date()) + "/";
		if (!new File(path).exists()) {
			new File(path).mkdirs();
		}
		return path;
	}

	private void writeToJson(final String filename, final List<UnitContext> cfgs) {
		OutputStream os = null;

		try {
			os = new FileOutputStream(filename);
			IOUtils.write(JSON.toJSONString(cfgs, SerializerFeature.PrettyFormat), os, "UTF-8");
			if (LOG.isInfoEnabled()) {
				LOG.info("write {} json config to {} succeed", cfgs.size(), filename);
			}
		} catch (Exception e) {
			LOG.error("exception when write json config to file:{}, detail: {}", filename,
					ExceptionUtils.exception2detail(e));
		} finally {
			IOUtils.closeQuietly(os);
		}
	}

	private List<UnitContext> getUnitContexts(final Map<String, List<UnitContext>> unitCtxs, final String group) {
		List<UnitContext> ret = unitCtxs.get(group);
		if (null == ret) {
			ret = new ArrayList<UnitContext>();
			unitCtxs.put(group, ret);
		}
		return ret;
	}

	private void restoreUnitsFromJson(final File jsonsDir) {
		if (null != jsonsDir && jsonsDir.exists() && jsonsDir.isDirectory()) {
			final int count = createUnitsByContexts(new ArrayList<UnitContext>() {
				private static final long serialVersionUID = 1L;
				{
					Collections.sort(loadUnitContexts(getUnitsJsonFiles(jsonsDir), this), UNITCTX_ASCEND_COMPARATOR);
				}
			});

			if (LOG.isInfoEnabled()) {
				LOG.info("restore {} units from {}.", count, jsonsDir.getPath());
			}
		} else {
			LOG.warn("jsons directory {} not exist or not directory, restore units failed.",
					(null != jsonsDir ? jsonsDir.getPath() : null));
		}
	}

	private int createUnitsByContexts(final List<UnitContext> allctx) {
		int unitsCreated = 0;
		for (UnitContext ctx : allctx) {
			try {
				if (null != createUnit(ctx.getGroup(), ctx.getName(), ctx.getSource(), ctx.getParams())) {
					unitsCreated++;
				}
			} catch (Exception e) {
				LOG.error("exception when createUnit for {}, detail:{}", ctx, ExceptionUtils.exception2detail(e));
			}
		}

		return unitsCreated;
	}

	/**
	 * @param cfgs
	 * @param allctx
	 */
	private List<UnitContext> loadUnitContexts(final File[] cfgs, final List<UnitContext> allctx) {
		for (File cfg : cfgs) {
			InputStream is = null;

			try {
				is = new FileInputStream(cfg);
				final UnitContext[] ctxs = JSON.parseObject(IOUtils.toString(is, "UTF-8"), UnitContext[].class);
				for (UnitContext ctx : ctxs) {
					ctx.setGroup(genGroupString(cfg));
				}
				allctx.addAll(Arrays.asList(ctxs));
			} catch (Exception e) {
				LOG.error("exception when load json config from file:{}, detail: {}", cfg.getPath(),
						ExceptionUtils.exception2detail(e));
			} finally {
				IOUtils.closeQuietly(is);
			}
		}

		return allctx;
	}

	private String genGroupString(final File cfg) {
		if (cfg.getName().endsWith(".json")) {
			return cfg.getName().substring(0, cfg.getName().length() - 5);
		} else {
			return cfg.getName();
		}
	}

	/**
	 * @param cfgdir
	 * @return
	 */
	private File[] getUnitsJsonFiles(final File cfgdir) {
		return cfgdir.listFiles(new FileFilter() {

			@Override
			public boolean accept(File f) {
				return f.getName().endsWith(".json");
			}
		});
	}

	private File activedUnitsJsonsDir() {
		final File parent = new File(this._jsonPath + "/");
		if (parent.exists()) {
			final File[] dirs = parent.listFiles(new FileFilter() {

				@Override
				public boolean accept(final File f) {
					return f.isDirectory();
				}
			});

			if (null != dirs && dirs.length > 0) {
				Arrays.sort(dirs, new Comparator<File>() {

					@Override
					public int compare(final File o1, final File o2) {
						return o1.getName().compareTo(o2.getName());
					}
				});

				final File lastDir = dirs[dirs.length - 1];
				if (LOG.isInfoEnabled()) {
					LOG.info("using {} as actived units json dir, try to load jsons", lastDir.getPath());
				}
				return lastDir;
			}
		}

		return null;
	}

	@Override
	public String genUnitsOrderAsJson() {

		final List<UnitContext> ctxs = new ArrayList<UnitContext>();

		for (Triple<String, String, AbstractApplicationContext> triple : this._units.values()) {
			final UnitMXBean unit = (UnitMXBean) _unitsRegister
					.getMBean(genUnitSuffix(triple.getFirst(), triple.getSecond()));

			if (null != unit) {
				ctxs.add(new UnitContext().setGroup(unit.getGroup()).setName(unit.getName()).setOrder(unit.getOrder()));
			}
		}

		Collections.sort(ctxs, UNITCTX_ASCEND_COMPARATOR);

		return JSON.toJSONString(ctxs, new PropertyFilter() {

			@Override
			public boolean apply(final Object source, final String name, final Object value) {
				return "group".equals(name) || "name".equals(name);
			}
		}, SerializerFeature.PrettyFormat);
	}

	@Override
	@SuppressWarnings("unchecked")
	public void resetUnitsOrderByJson(final String json) {

		final UnitContext[] ctxs = JSON.parseObject(json, UnitContext[].class);

		final Object[] triples = this._units.values().toArray();

		// reserved new unit order
		this._order.set(triples.length * 10);

		final List<Pair<String, String>> orgs = new ArrayList<Pair<String, String>>() {
			private static final long serialVersionUID = 1L;

			{
				for (Object o : triples) {
					final Triple<String, String, AbstractApplicationContext> triple = (Triple<String, String, AbstractApplicationContext>) o;

					this.add(Pair.of(triple.getFirst(), triple.getSecond()));
				}
			}
		};

		int order = 0;

		for (UnitContext ctx : ctxs) {
			order = resetUnitOrder(order, ctx.getGroup(), ctx.getName());

			orgs.remove(Pair.of(ctx.getGroup(), ctx.getName()));
		}

		// deal with left group and name pair
		for (Pair<String, String> pair : orgs) {
			order = resetUnitOrder(order, pair.getFirst(), pair.getSecond());
		}
	}

	/**
	 * @param order
	 * @param group
	 * @return
	 */
	private int resetUnitOrder(final int order, final String group, final String name) {
		final UnitMXBean unit = (UnitMXBean) this._unitsRegister.getMBean(genUnitSuffix(group, name));
		if (null != unit) {
			unit.setOrder(order);
			return order + 10;
		} else {
			LOG.warn("can't found unit with group={},name={}, reset unit's order failed", group, name);
			return order;
		}
	}

	@Override
	public String genUnitContextsAsJson() {

		final Map<String, List<UnitContext>> unitCtxs = units2UnitContexts(new HashMap<String, List<UnitContext>>());

		for (Map.Entry<String, List<UnitContext>> entry : unitCtxs.entrySet()) {

			Collections.sort(entry.getValue(), UNITCTX_ASCEND_COMPARATOR);
		}

		return JSON.toJSONString(unitCtxs, SerializerFeature.PrettyFormat);
	}

	@Override
	public void createUnitsFromJson(final String json) {

		final Map<String, UnitContext[]> unitCtxs = JSON.parseObject(json, new ParameterizedType() {

			@Override
			public Type[] getActualTypeArguments() {
				// key, value
				return new Type[] { String.class, UnitContext[].class };
			}

			@Override
			public Type getRawType() {
				return HashMap.class;
			}

			@Override
			public Type getOwnerType() {
				return null;
			}
		});

		final int count = createUnitsByContexts(new ArrayList<UnitContext>() {
			private static final long serialVersionUID = 1L;
			{
				// fill group field
				for (Map.Entry<String, UnitContext[]> entry : unitCtxs.entrySet()) {
					for (UnitContext ctx : entry.getValue()) {
						ctx.setGroup(entry.getKey());
						this.add(ctx);
					}
				}
				Collections.sort(this, UNITCTX_ASCEND_COMPARATOR);
			}
		});
		if (LOG.isInfoEnabled()) {
			LOG.info("create {} units from json.", count);
		}
	}

	/**
	 * @param unitSource
	 * @param configurer
	 * @return
	 */
	private AbstractApplicationContext createConfigurableApplicationContext(final String unitSource,
			final PropertyPlaceholderConfigurer configurer) {
		final AbstractApplicationContext topCtx = new ClassPathXmlApplicationContext(
				new String[] { "xj/toolkit/ebus/spring/unitParent.xml" }, this._root);

		final PropertyConfigurerFactory factory = topCtx.getBean(PropertyConfigurerFactory.class);

		factory.setConfigurer(configurer);

		final AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
				new String[] { "xj/toolkit/ebus/spring/Configurable.xml", unitSource }, topCtx);
		return ctx;
	}

	private String[] searchUnitSourceOf(final String[] sourcePatterns) {

		if (LOG.isDebugEnabled()) {
			LOG.debug("try to match pattern: {}", Arrays.toString(sourcePatterns));
		}
		final List<String> sources = new ArrayList<String>();
		try {
			final Map<URL, String[]> allRes = PackageUtils.getAllCPResourceAsPathlike();
			for (Map.Entry<URL, String[]> entry : allRes.entrySet()) {
				for (String res : entry.getValue()) {
					if (LOG.isTraceEnabled()) {
						LOG.trace("found resource: {}", res);
					}
					for (String pattern : sourcePatterns) {
						if (SelectorUtils.match(pattern, res)) {
							if (LOG.isDebugEnabled()) {
								LOG.debug("found matched unit source: {}", res);
							}
							sources.add(res);
						}
					}
				}
			}
		} catch (IOException e) {
			LOG.error("exception when search pattern {}, detail : {}", Arrays.toString(sourcePatterns),
					ExceptionUtils.exception2detail(e));
		}

		return sources.isEmpty() ? null : sources.toArray(new String[sources.size()]);
	}

	private String _jsonPath = ".";

	private boolean _restoreUnitsFromJson = false;

	private String[] _sourcePatterns;

	private final Resource[] _rootPropertyFiles;

	private final MBeanRegisterSupport _sourcesRegister;

	private final ApplicationContext _root;

	private final MBeanRegisterSupport _unitsRegister;

	private final Map<UUID, Triple<String, String, AbstractApplicationContext>> _units = new ConcurrentHashMap<UUID, Triple<String, String, AbstractApplicationContext>>();

	private final AtomicInteger _logidx = new AtomicInteger(0);

	private final AtomicInteger _order = new AtomicInteger(0);

	private final Queue<String> _logs = new ConcurrentLinkedQueue<String>();

}
